// Parts Copyright 2008 Newgrove Consultants Limited
// Modified By John Diss 
// From original work:
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using GeoAPI.Coordinates;
using GeoAPI.Geometries;

/*
 * 
 * John Diss 17 Jan 2008:
 * Modified from a version taken from the Net Topology Suite Pre GeoAPI refactor.
 * 
 * 
 * 
 */

namespace MsSqlSpatial.IO.GML2
{
    /// <summary>
    /// Reads a GML document and creates a representation of the features based or NetTopologySuite model.
    /// Uses GML 2.1.1 <c>Geometry.xsd</c> schema for base for features.
    /// </summary>
    public static class GMLReader
    {
        /// <summary>
        /// Read a GML document and returns relative <c>Geometry</c>.
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public static IGeometry Read(XmlDocument document, IGeometryFactory geometryFactory)
        {
            var reader = new XmlTextReader(new StringReader(document.InnerXml));
            return Read(reader, geometryFactory);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xmlText"></param>
        /// <returns></returns>
        public static IGeometry Read(string xmlText, IGeometryFactory geometryFactory)
        {
            var reader = new XmlTextReader(new StringReader(xmlText));
            return Read(reader, geometryFactory);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stringReader"></param>
        /// <returns></returns>
        public static IGeometry Read(StringReader stringReader, IGeometryFactory geometryFactory)
        {
            var reader = new XmlTextReader(stringReader);
            return Read(reader, geometryFactory);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static IGeometry Read(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            // Read the first two childs...
            reader.Read();
            reader.Read();

            // ... then seek for geometry element
            if (reader.IsStartElement("Point", GMLElements.gmlNS))
                return ReadPoint(reader, geometryFactory);
            else if (reader.IsStartElement("LineString", GMLElements.gmlNS))
                return ReadLineString(reader, geometryFactory);
            else if (reader.IsStartElement("Polygon", GMLElements.gmlNS))
                return ReadPolygon(reader, geometryFactory);
            else if (reader.IsStartElement("MultiPoint", GMLElements.gmlNS))
                return ReadMultiPoint(reader, geometryFactory);
            else if (reader.IsStartElement("MultiLineString", GMLElements.gmlNS))
                return ReadMultiLineString(reader, geometryFactory);
            else if (reader.IsStartElement("MultiPolygon", GMLElements.gmlNS))
                return ReadMultiPolygon(reader, geometryFactory);
            else if (reader.IsStartElement("MultiGeometry", GMLElements.gmlNS))
                return ReadGeometryCollection(reader, geometryFactory);
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// Reads the coordinate.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns></returns>
        private static ICoordinate ReadCoordinate(XmlTextReader reader, ICoordinateFactory coordinateFactory)
        {
            double x = 0, y = 0;
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("X", GMLElements.gmlNS))
                        {
                            reader.Read(); // Jump to X value
                            x = XmlConvert.ToDouble(reader.Value);
                        }
                        else if (reader.IsStartElement("Y", GMLElements.gmlNS))
                        {
                            reader.Read(); // Jump to Y value
                            y = XmlConvert.ToDouble(reader.Value);
                        }
                        break;

                    case XmlNodeType.EndElement:
                        if (reader.Name == GMLElements.gmlPrefix + ":coord")
                            return coordinateFactory.Create(x, y);
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static IPoint ReadPoint(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("coord", GMLElements.gmlNS))

                            return geometryFactory.CreatePoint(ReadCoordinate(reader, geometryFactory.CoordinateFactory));
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static ILineString ReadLineString(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            var coordinates = new List<ICoordinate>();
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("coord", GMLElements.gmlNS))
                            coordinates.Add(ReadCoordinate(reader, geometryFactory.CoordinateFactory));
                        break;

                    case XmlNodeType.EndElement:
                        return geometryFactory.CreateLineString(coordinates);

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static ILinearRing ReadLinearRing(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            return geometryFactory.CreateLinearRing(ReadLineString(reader, geometryFactory).Coordinates);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static IPolygon ReadPolygon(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            ILinearRing exterior = null;
            var interiors = new List<ILinearRing>();
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("outerBoundaryIs", GMLElements.gmlNS))
                            exterior = ReadLinearRing(reader, geometryFactory);
                        else if (reader.IsStartElement("innerBoundaryIs", GMLElements.gmlNS))
                            interiors.Add(ReadLinearRing(reader, geometryFactory));
                        break;

                    case XmlNodeType.EndElement:
                        if (reader.Name == GMLElements.gmlPrefix + ":Polygon")
                            return geometryFactory.CreatePolygon(exterior, interiors);
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static IMultiPoint ReadMultiPoint(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            var points = new List<IPoint>();
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("pointMember", GMLElements.gmlNS))
                            points.Add(ReadPoint(reader, geometryFactory));
                        break;

                    case XmlNodeType.EndElement:
                        if (reader.Name == GMLElements.gmlPrefix + ":MultiPoint")
                            return geometryFactory.CreateMultiPoint(points);
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static IMultiLineString ReadMultiLineString(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            var lines = new List<ILineString>();
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("lineStringMember", GMLElements.gmlNS))
                            lines.Add(ReadLineString(reader, geometryFactory));
                        break;

                    case XmlNodeType.EndElement:
                        if (reader.Name == GMLElements.gmlPrefix + ":MultiLineString")
                            return geometryFactory.CreateMultiLineString(lines);
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static IMultiPolygon ReadMultiPolygon(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            var polygons = new List<IPolygon>();
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("polygonMember", GMLElements.gmlNS))
                            polygons.Add(ReadPolygon(reader, geometryFactory));
                        break;

                    case XmlNodeType.EndElement:
                        if (reader.Name == GMLElements.gmlPrefix + ":MultiPolygon")
                            return geometryFactory.CreateMultiPolygon(polygons);
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        private static IGeometryCollection ReadGeometryCollection(XmlTextReader reader, IGeometryFactory geometryFactory)
        {
            var collection = new List<IGeometry>();
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.IsStartElement("Point", GMLElements.gmlNS))
                            collection.Add(ReadPoint(reader, geometryFactory));
                        else if (reader.IsStartElement("LineString", GMLElements.gmlNS))
                            collection.Add(ReadLineString(reader, geometryFactory));
                        else if (reader.IsStartElement("Polygon", GMLElements.gmlNS))
                            collection.Add(ReadPolygon(reader, geometryFactory));
                        else if (reader.IsStartElement("MultiPoint", GMLElements.gmlNS))
                            collection.Add(ReadMultiPoint(reader, geometryFactory));
                        else if (reader.IsStartElement("MultiLineString", GMLElements.gmlNS))
                            collection.Add(ReadMultiLineString(reader, geometryFactory));
                        else if (reader.IsStartElement("MultiPolygon", GMLElements.gmlNS))
                            collection.Add(ReadMultiPolygon(reader, geometryFactory));
                        else if (reader.IsStartElement("MultiGeometry", GMLElements.gmlNS))
                            collection.Add(ReadGeometryCollection(reader, geometryFactory));
                        break;

                    case XmlNodeType.EndElement:
                        if (reader.Name == GMLElements.gmlPrefix + ":MultiGeometry")
                            return geometryFactory.CreateGeometryCollection(collection);
                        break;

                    default:
                        break;
                }
            }
            throw new ArgumentException("ShouldNeverReachHere!");
        }
    }
}